// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/child/service_worker/service_worker_provider_context.h"

#include <utility>

#include "base/macros.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/child/child_thread_impl.h"
#include "content/child/service_worker/service_worker_dispatcher.h"
#include "content/child/service_worker/service_worker_handle_reference.h"
#include "content/child/service_worker/service_worker_registration_handle_reference.h"
#include "content/child/thread_safe_sender.h"
#include "content/child/worker_thread_registry.h"

namespace content {

class ServiceWorkerProviderContext::Delegate {
public:
    virtual ~Delegate() { }
    virtual void AssociateRegistration(
        std::unique_ptr<ServiceWorkerRegistrationHandleReference> registration,
        std::unique_ptr<ServiceWorkerHandleReference> installing,
        std::unique_ptr<ServiceWorkerHandleReference> waiting,
        std::unique_ptr<ServiceWorkerHandleReference> active)
        = 0;
    virtual void DisassociateRegistration() = 0;
    virtual void GetAssociatedRegistration(
        ServiceWorkerRegistrationObjectInfo* info,
        ServiceWorkerVersionAttributes* attrs)
        = 0;
    virtual bool HasAssociatedRegistration() = 0;
    virtual void SetController(
        std::unique_ptr<ServiceWorkerHandleReference> controller)
        = 0;
    virtual ServiceWorkerHandleReference* controller() = 0;
};

// Delegate class for ServiceWorker client (Document, SharedWorker, etc) to
// keep the associated registration and the controller until
// ServiceWorkerContainer is initialized.
class ServiceWorkerProviderContext::ControlleeDelegate
    : public ServiceWorkerProviderContext::Delegate {
public:
    ControlleeDelegate() { }
    ~ControlleeDelegate() override { }

    void AssociateRegistration(
        std::unique_ptr<ServiceWorkerRegistrationHandleReference> registration,
        std::unique_ptr<ServiceWorkerHandleReference> installing,
        std::unique_ptr<ServiceWorkerHandleReference> waiting,
        std::unique_ptr<ServiceWorkerHandleReference> active) override
    {
        DCHECK(!registration_);
        registration_ = std::move(registration);
    }

    void DisassociateRegistration() override
    {
        controller_.reset();
        registration_.reset();
    }

    void SetController(
        std::unique_ptr<ServiceWorkerHandleReference> controller) override
    {
        DCHECK(registration_);
        DCHECK(!controller || controller->handle_id() != kInvalidServiceWorkerHandleId);
        controller_ = std::move(controller);
    }

    bool HasAssociatedRegistration() override { return !!registration_; }

    void GetAssociatedRegistration(
        ServiceWorkerRegistrationObjectInfo* info,
        ServiceWorkerVersionAttributes* attrs) override
    {
        NOTREACHED();
    }

    ServiceWorkerHandleReference* controller() override
    {
        return controller_.get();
    }

private:
    std::unique_ptr<ServiceWorkerRegistrationHandleReference> registration_;
    std::unique_ptr<ServiceWorkerHandleReference> controller_;

    DISALLOW_COPY_AND_ASSIGN(ControlleeDelegate);
};

// Delegate class for ServiceWorkerGlobalScope to keep the associated
// registration and its versions until the execution context is initialized.
class ServiceWorkerProviderContext::ControllerDelegate
    : public ServiceWorkerProviderContext::Delegate {
public:
    ControllerDelegate() { }
    ~ControllerDelegate() override { }

    void AssociateRegistration(
        std::unique_ptr<ServiceWorkerRegistrationHandleReference> registration,
        std::unique_ptr<ServiceWorkerHandleReference> installing,
        std::unique_ptr<ServiceWorkerHandleReference> waiting,
        std::unique_ptr<ServiceWorkerHandleReference> active) override
    {
        DCHECK(!registration_);
        registration_ = std::move(registration);
        installing_ = std::move(installing);
        waiting_ = std::move(waiting);
        active_ = std::move(active);
    }

    void DisassociateRegistration() override
    {
        // ServiceWorkerGlobalScope is never disassociated.
        NOTREACHED();
    }

    void SetController(
        std::unique_ptr<ServiceWorkerHandleReference> controller) override
    {
        NOTREACHED();
    }

    bool HasAssociatedRegistration() override { return !!registration_; }

    void GetAssociatedRegistration(
        ServiceWorkerRegistrationObjectInfo* info,
        ServiceWorkerVersionAttributes* attrs) override
    {
        DCHECK(HasAssociatedRegistration());
        *info = registration_->info();
        if (installing_)
            attrs->installing = installing_->info();
        if (waiting_)
            attrs->waiting = waiting_->info();
        if (active_)
            attrs->active = active_->info();
    }

    ServiceWorkerHandleReference* controller() override
    {
        NOTREACHED();
        return nullptr;
    }

private:
    std::unique_ptr<ServiceWorkerRegistrationHandleReference> registration_;
    std::unique_ptr<ServiceWorkerHandleReference> installing_;
    std::unique_ptr<ServiceWorkerHandleReference> waiting_;
    std::unique_ptr<ServiceWorkerHandleReference> active_;

    DISALLOW_COPY_AND_ASSIGN(ControllerDelegate);
};

ServiceWorkerProviderContext::ServiceWorkerProviderContext(
    int provider_id,
    ServiceWorkerProviderType provider_type,
    ThreadSafeSender* thread_safe_sender)
    : provider_id_(provider_id)
    , main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get())
    , thread_safe_sender_(thread_safe_sender)
{
    if (provider_type == SERVICE_WORKER_PROVIDER_FOR_CONTROLLER)
        delegate_.reset(new ControllerDelegate);
    else
        delegate_.reset(new ControlleeDelegate);

    ServiceWorkerDispatcher* dispatcher = ServiceWorkerDispatcher::GetOrCreateThreadSpecificInstance(
        thread_safe_sender_.get(), main_thread_task_runner_.get());
    dispatcher->AddProviderContext(this);
}

ServiceWorkerProviderContext::~ServiceWorkerProviderContext()
{
    if (ServiceWorkerDispatcher* dispatcher = ServiceWorkerDispatcher::GetThreadSpecificInstance()) {
        // Remove this context from the dispatcher living on the main thread.
        dispatcher->RemoveProviderContext(this);
    }
}

void ServiceWorkerProviderContext::OnAssociateRegistration(
    std::unique_ptr<ServiceWorkerRegistrationHandleReference> registration,
    std::unique_ptr<ServiceWorkerHandleReference> installing,
    std::unique_ptr<ServiceWorkerHandleReference> waiting,
    std::unique_ptr<ServiceWorkerHandleReference> active)
{
    DCHECK(main_thread_task_runner_->RunsTasksOnCurrentThread());
    delegate_->AssociateRegistration(std::move(registration),
        std::move(installing), std::move(waiting),
        std::move(active));
}

void ServiceWorkerProviderContext::OnDisassociateRegistration()
{
    DCHECK(main_thread_task_runner_->RunsTasksOnCurrentThread());
    delegate_->DisassociateRegistration();
}

void ServiceWorkerProviderContext::OnSetControllerServiceWorker(
    std::unique_ptr<ServiceWorkerHandleReference> controller)
{
    DCHECK(main_thread_task_runner_->RunsTasksOnCurrentThread());
    delegate_->SetController(std::move(controller));
}

void ServiceWorkerProviderContext::GetAssociatedRegistration(
    ServiceWorkerRegistrationObjectInfo* info,
    ServiceWorkerVersionAttributes* attrs)
{
    DCHECK(!main_thread_task_runner_->RunsTasksOnCurrentThread());
    delegate_->GetAssociatedRegistration(info, attrs);
}

bool ServiceWorkerProviderContext::HasAssociatedRegistration()
{
    return delegate_->HasAssociatedRegistration();
}

ServiceWorkerHandleReference* ServiceWorkerProviderContext::controller()
{
    DCHECK(main_thread_task_runner_->RunsTasksOnCurrentThread());
    return delegate_->controller();
}

void ServiceWorkerProviderContext::DestructOnMainThread() const
{
    if (!main_thread_task_runner_->RunsTasksOnCurrentThread() && main_thread_task_runner_->DeleteSoon(FROM_HERE, this)) {
        return;
    }
    delete this;
}

} // namespace content
